<template>
	<div
		:class="['app-editor-container', 'app-rich-text', `app-rich-text-${name}`]"
		:id="tinymceFlag"
	/>
</template>
<script setup lang="ts">
import axios from 'axios';
import tinymce from 'tinymce/tinymce';
// import 'tinymce/themes/modern';
import 'tinymce/themes/silver';
import 'tinymce/plugins/link';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/table';
import 'tinymce/plugins/image';
import 'tinymce/plugins/imagetools';
import 'tinymce/plugins/codesample';
import 'tinymce/plugins/code';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/fullscreen';
import 'tinymce/icons/default/icons.min.js';
import type { IEvent } from '@/core';
import { richTextProps } from './richtext';
import {
	getDownloadUrl,
	getRequestingHeader,
	getUploadUrl,
} from '@/hooks/use-editor';
import { computedNavData, createUUID, getLocal } from '@/core';
import { Http } from '@/http';
import { i18n } from '@/locales';
import { useAssetPath } from '@/hooks/use-app';
import { useEditorTypeParams } from '@/hooks/use-editor';
const tinymceCode: any = tinymce;
interface EditorEmit {
	(name: 'component-action', value: IEvent<string>): void;
}
const props = defineProps(richTextProps);
const emit = defineEmits<EditorEmit>();
// 类型校验
const { validateConfig } = useEditorTypeParams();
validateConfig();
let uploadUrl = getUploadUrl();
let downloadUrl = getDownloadUrl();
const headers = getRequestingHeader();
const uploadParams = computedNavData(
	props.data,
	props.context,
	props.viewParams,
	props.uploadParams
);
const exportParams = computedNavData(
	props.data,
	props.context,
	props.viewParams,
	props.exportParams
);
let uuid: string = createUUID();
const curLocal: string = getLocal();
let richTextEditor: any = null;
let isActivated = true;
const imgSrcList: any[] = [];
const loadingImgMap: Map<string, any> = new Map();
const successImgMap: Map<string, any> = new Map();
const tinymceFlag = createUUID();
onBeforeMount(() => {
	const tempUrl = import.meta.env.VITE_BASE_URL;
	const uploadIndex = uploadUrl.indexOf(tempUrl);
	const downloadIndex = downloadUrl.indexOf(tempUrl);
	if (uploadIndex > -1) {
		uploadUrl = uploadUrl.substring(uploadIndex + tempUrl.length);
	}
	if (downloadIndex > -1) {
		downloadUrl = downloadUrl.substring(downloadIndex + tempUrl.length);
	}
});

watch(
	() => i18n.global.locale.value,
	(newVal: any, oldVal: any) => {
		if (newVal !== oldVal && isActivated) {
			uuid = createUUID();
			const tempEl = createEl(uuid);
			const parentel = document.getElementById(tinymceFlag);
			if (parentel) {
				parentel.innerHTML = '';
				parentel.appendChild(tempEl);
			}
			init(tempEl);
		}
	}
);
watch(
	() => [props.disabled, props.readonly, props.value],
	(newVal: any, oldVal: any) => {
		renderRichText();
	}
);

const createEl = (uuid: string) => {
	const textarea = document.createElement('textarea');
	textarea.id = uuid;
	textarea.classList.add('app-rich-text__textarea');
	return textarea;
};

const getImgUrl = (html: any) => {
	let imgs: Array<any> | null =
		html.match(/<img.*?(?:>|\/>)/gi) != null
			? html.match(/<img.*?(?:>|\/>)/gi)
			: [];
	if (imgs && imgs.length > 0 && imgSrcList && imgSrcList.length > 0) {
		imgs.forEach((img: any, index: number) => {
			var reg = /data:image\/.*;base64,.*(?=[\'\"])/;
			if (img.match(reg) != null) {
				let base64: any = img.match(reg)[0];
				const _imgsrc = imgSrcList.find((_imgsrc: any) =>
					Object.is(_imgsrc.key, base64)
				);
				if (_imgsrc) {
					const newImg = img.replace(reg, '{' + _imgsrc.value + '}');
					html = html.replace(img, newImg);
				}
			}
		});
	}
	return html;
};

const getImgUrlBase64 = async (html: any) => {
	let imgs: Array<any> | null =
		html.match(/<img.*?(?:>|\/>)/gi) != null
			? html.match(/<img.*?(?:>|\/>)/gi)
			: [];
	if (imgs && imgs.length > 0) {
		for (let item of imgs) {
			if (item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/gi) != null) {
				let src: any = item.match(/src=[\'\"]?([^\'\"]*)[\'\"]?/gi)[0];
				src = await getImgURLOfBase64(
					src.substring(5, src.length - 1),
					loadingImgMap,
					successImgMap
				);
				const image = item.replace(
					/src=[\'\"]?([^\'\"]*)[\'\"]?/i,
					'src="' + src + '"'
				);
				html = html.replace(item, image);
			}
		}
	}
	return html;
};

/**
 * @description 获取base64的图片url
 * @param {string} url
 * @param {Map<string, any>} loadingImgMap
 * @param {Map<string, any>} successImgMap
 * @return {*}
 * @memberof EditorBase
 */
const getImgURLOfBase64 = (
	url: string,
	loadingImgMap: Map<string, any>,
	successImgMap: Map<string, any>
): Promise<string> => {
	return new Promise((resolve) => {
		let img = '/';
		// 富文本CV上传图片与鼠标移出抛值冲突问题,上传成功回调还没执行时就抛值
		var reg =
			/^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i;
		if (reg.test(url)) {
			return resolve(url);
		}
		// 缓存中有从缓存中拿
		if (successImgMap.get(url)) {
			let img = successImgMap.get(url);
			resolve(img);
		}
		const callback: Function = (url: string, promise: Promise<any>) => {
			promise
				.then((response: any) => {
					if (response && response.status === 200 && response.data) {
						// 获取文件名
						const disposition = response.headers['content-disposition'];
						const filename = disposition.split('filename=')[1];
						let type = 'image/png';
						if (filename && filename.indexOf('.') > 0) {
							const start = filename.lastIndexOf('.');
							const expandedName = filename.substring(start + 1);
							if (
								expandedName.match(
									/(bmp|jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp)/gi
								) != null
							) {
								type = 'image/' + expandedName;
							} else {
								resolve(img);
							}
						}
						let blob = new Blob([response.data], { type: type });
						blobToBase64(blob).then((res: any) => {
							// 转化后的base64
							img = `${res}`;
							// 缓存图片
							successImgMap.set(url, img);
							resolve(img);
						});
					} else {
						resolve(img);
					}
				})
				.catch((result: any) => {
					return resolve(img);
				});
		};
		// 加载中
		if (loadingImgMap.get(url)) {
			callback(url, loadingImgMap.get(url));
		} else {
			let _url = url;
			if (!Object.is('/', _url.substring(0, 1))) {
				_url = '/' + _url;
			}
			let result: Promise<any> = axios({
				method: 'get',
				url: _url,
				responseType: 'blob',
			});
			loadingImgMap.set(url, result);
			callback(url, result);
		}
	});
};

/**
 * @description blob对象转base64
 * @param {Blob} blob
 * @return {*}  {Promise<string>}
 * @memberof EditorBase
 */
const blobToBase64 = (blob: Blob): Promise<string> => {
	return new Promise((resolve, reject) => {
		const fileReader = new FileReader();
		fileReader.onload = (e: any) => {
			resolve(e.target.result);
		};
		fileReader.readAsDataURL(blob);
		fileReader.onerror = () => {
			reject(new Error('blobToBase64 error'));
		};
	});
};

const uploadFile = (url: string, formData: any) => {
	let _url = url;
	const baseUrl = import.meta.env.VITE_BASE_URL;
	if (url.startsWith(baseUrl)) {
		_url = url.replace(baseUrl, '');
	}
	return new Promise((resolve: any, reject: any) => {
		try {
			Http.getHttp()({
				method: 'post',
				url: _url,
				data: formData,
				headers: { 'Content-Type': 'image/png', Accept: 'application/json' },
			})
				.then((response: any) => {
					if (response && response.status === 200) {
						resolve(response.data);
					} else {
						App.getNotificationHelper().warning(
							App.ts('common.generic.error'),
							App.ts('common.fileupload.uploaderror', '上传失败')
						);
						reject(response);
					}
				})
				.catch((error: any) => {
					App.getNotificationHelper().warning(
						App.ts('common.generic.error'),
						App.ts('common.fileupload.uploaderror', '上传失败')
					);
				});
		} catch {
			App.getNotificationHelper().warning(
				App.ts('common.generic.error'),
				App.ts('common.fileupload.uploaderror', '上传失败')
			);
		}
	});
};
const init = (el: any) => {
	tinymceCode.init({
		// selector: '#' + uuid,
		target: el,
		width: 'calc( 100% - 2px )',
		height: props.height
			? props.height
			: props.itemHeight && props.itemHeight > 1
			? props.itemHeight
			: props.height,
		min_height: props.height
			? Math.min(props.height, 300)
			: props.itemHeight && props.itemHeight > 1
			? Math.min(props.itemHeight, 300)
			: 300,
		branding: false,
		plugins: [
			'link',
			'paste',
			'table',
			'image',
			'codesample',
			'code',
			'fullscreen',
			'preview',
			// 'quickbars',
			'fullscreen',
		],
		toolbar:
			'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | link image | preview code fullscreen',
		contextmenu: 'cut copy paste pastetext inserttable link ',
		// quickbars_insert_toolbar: false,
		// quickbars_selection_toolbar: 'forecolor fontsizeselect fontselect',
		codesample_languages: [
			{ text: 'HTML/XML', value: 'markup' },
			{ text: 'JavaScript', value: 'javascript' },
			{ text: 'CSS', value: 'css' },
			{ text: 'PHP', value: 'php' },
			{ text: 'Ruby', value: 'ruby' },
			{ text: 'Python', value: 'python' },
			{ text: 'Java', value: 'java' },
			{ text: 'C', value: 'c' },
			{ text: 'C#', value: 'csharp' },
			{ text: 'C++', value: 'cpp' },
		],
		paste_data_images: true,
		content_css: [],
		codesample_content_css: useAssetPath('/assets/tinymce/prism.css'),
		skin_url: useAssetPath('/assets/tinymce/skins/lightgray/ui/oxide'),
		language_url: useAssetPath(`/assets/tinymce/langs/${curLocal}.js`),
		language: curLocal,
		setup: (editor: any) => {
			richTextEditor = editor;
			editor.on('blur', (event: any) => {
				let content = editor.getContent();
				content = getImgUrl(content);
				emit('component-action', {
					name: `${props.name}`,
					action: 'valueChange',
					data: content,
				});
				emit('component-action', {
					name: `${props.name}`,
					action: 'editorBlur',
					data: content,
				});
			});
			editor.on('focus', (event: any) => {
				let content = editor.getContent();
				content = getImgUrl(content);
				emit('component-action', {
					name: `${props.name}`,
					action: 'editorFocus',
					data: content,
				});
			});
		},
		images_upload_handler: (bolbinfo: any, success: any, failure: any) => {
			const formData = new FormData();
			formData.append('file', bolbinfo.blob(), bolbinfo.filename());
			let _url = uploadUrl;
			if (Object.keys(uploadParams).length > 0) {
				_url += '?';
				Object.keys(uploadParams).forEach((item: any, i: any) => {
					_url += `${item}=${uploadParams[item]}`;
					if (i < Object.keys(uploadParams).length - 1) {
						_url += '&';
					}
				});
			}
			uploadFile(_url, formData).then(
				(file: any) => {
					if (file.filename) {
						const id: string = file.fileid;
						const url = `${downloadUrl}/${id}`;
						getImgURLOfBase64(url, loadingImgMap, successImgMap).then(
							(response: any) => {
								const imgsrc = imgSrcList.find((imgsrc: any) =>
									Object.is(response, imgsrc.key)
								);
								if (!imgsrc) {
									const item: any = { key: response, value: createUUID() };
									imgSrcList.push(item);
								}
								success(response);
								let content = richTextEditor.getContent();
								content = getImgUrl(content);
								emit('component-action', {
									name: `${props.name}`,
									action: 'valueChange',
									data: content,
								});
							}
						);
					}
					if (exportParams.length > 0) {
						downloadUrl += '?';
						exportParams.forEach((item: any, i: any) => {
							downloadUrl += `${Object.keys(item)[0]}=${
								Object.values(item)[0]
							}`;
							if (i < exportParams.length - 1) {
								downloadUrl += '&';
							}
						});
					}
				},
				(error: any) => {
					console.log(error);
					failure('HTTP Error: ' + error.status);
				}
			);
		},
		init_instance_callback: (editor: any) => {
			richTextEditor = editor;
			const url =
				downloadUrl.indexOf('../') === 0
					? downloadUrl.substring(3)
					: downloadUrl;
			let value = props.value && props.value.length > 0 ? props.value : '';
			value = value.replace(
				/\{(\w+)\.(bmp|jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp)\}/g,
				`${url}/$1`
			);
			getImgUrlBase64(value).then((newValue: any) => {
				if (richTextEditor) {
					richTextEditor.setContent(newValue);
				}
				if (props.disabled || props.readonly) {
					richTextEditor.setMode('readonly');
				}
			});
		},
	});
};
onMounted(() => {
	const el = createEl(uuid);
	const parentel = document.getElementById(tinymceFlag);
	if (parentel) {
		parentel.appendChild(el);
	}
	init(el);
});

const renderRichText = () => {
	uuid = createUUID();
	const tempEl = createEl(uuid);
	const parentel = document.getElementById(tinymceFlag);
	if (parentel) {
		parentel.innerHTML = '';
		parentel.appendChild(tempEl);
	}
	init(tempEl);
};

onActivated(() => {
	isActivated = true;
	renderRichText();
});
onDeactivated(() => {
	isActivated = false;
});
onUnmounted(() => {
	if (richTextEditor) {
		richTextEditor = null;
		tinymceCode.remove('#' + uuid);
	}
});
</script>
